package evo;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import evo.selectors.ISelector;

public class Evolution {
	
	private double _pMutate;
	private double _pCrossover;
	
	private ArrayList<IInstance> _currGen;
	private Random _r = new Random();

	private int _popSize;

	private ISelector _selector;
	
	
	public Evolution(IInstanceBuilder builder, int n, double pmutate, double pCrossover, ISelector selector)
	{
		this._pMutate = pmutate;
		this._pCrossover = pCrossover;
		this._currGen = new ArrayList<IInstance>();
		this._popSize = n;
		this._selector = selector;
		
		for (int i = 0; i < this._popSize; i++) {
			this._currGen.add(builder.createRandom());
		}
	}
	
	public void advanceGen()
	{
		ArrayList<IInstance> nextGen = new ArrayList<IInstance>();
		for (IInstance inst: this._currGen)
		{
			
			nextGen.add(inst);
			
			boolean crossover = _r.nextDouble() < this._pCrossover;
			if (crossover)
			{
				IInstance mate = randomInstance(this._currGen);
				IInstance child = inst.crossover(mate);
				nextGen.add(child);
			}
			
			boolean mutate = _r.nextDouble() < this._pMutate;
			if (mutate)
			{
				nextGen.add(inst.mutate());
			}
		}
		Map<IInstance, Double> nextGenerationMap = new HashMap<IInstance, Double>();
		for (IInstance instance : nextGen){
			instance.calculateFitness();
			nextGenerationMap.put(instance, instance.getFitness());
		}
		this._currGen = select(nextGenerationMap,this._popSize);
	}	
	
	
	public void advanceGen(int n)
	{
		for (int i = 0; i < n; i++) {
			this.advanceGen();
		}
	}
	
	
	private IInstance randomInstance(ArrayList<IInstance> collection)
	{
		int r = this._r.nextInt(collection.size());
		return collection.get(r);
	}
	
	private ArrayList<IInstance> select(Map<IInstance, Double> pool, int n)
	{

		return this._selector.select(pool,n);
	}

	public ArrayList<IInstance> getCurrentGen()
	{
		return this._currGen;
	}
	
	public double getAverageFitness()
	{
		double avg = 0;
		for (IInstance instance : this._currGen)
		{
			avg += instance.getFitness();
		}
		avg /= this._currGen.size();
		return avg;
	}
	
	public int getBestFitnessIndex()
	{
		int besti = 0;
		double bestf = 0;
		for (int i = 0; i < this._currGen.size(); i++) {
			double f = this._currGen.get(i).getFitness(); 
			if (f > bestf)
			{
				besti = i;
				bestf = f;
			}
		}
		return besti;
	}
	
	public double getBestFitness()
	{
		return this._currGen.get(this.getBestFitnessIndex()).getFitness();
	}
	
	public IInstance getBestInstance()
	{
		return this._currGen.get(this.getBestFitnessIndex());
	}
	
	public double getVarianceFitness()
	{
		double avg = this.getAverageFitness();
		double var = 0;
		for (IInstance instance : this._currGen)
		{
			var += Math.pow(instance.getFitness() - avg,2);
		}
		var /= this._currGen.size();
		return var;
	}
}
